/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		cmdline.cpp
 * PURPOSE:		command line options parsing
 *				
 */

namespace CmdLineParser
{

BOOL IsTextDiv(WCHAR WChar)
{
	return iswspace(WChar);
}

BOOL IsMultiEnumDiv(WCHAR WChar)
{
	return WChar == L'|' || IsTextDiv(WChar);
}

BOOL IsOptDiv(WCHAR WChar)
{
	return IsTextDiv(WChar);
}

BOOL IsQuote(WCHAR WChar)
{
	return WChar == L'\"';
}

void FORCEINLINE ProceedToQuote(PWSTR &Pos, PWSTR MaxPos)
{
	for(; Pos < MaxPos; Pos++)
		if(IsQuote(*Pos))
			break;
}

void SkipSpaces(PWSTR &Pos, PWSTR MaxPos)
{
	for(; Pos < MaxPos; Pos++)
		if(!*Pos || !iswspace(*Pos))
			return;
}

PWSTR ExtractThisXAndProceedNext(PWSTR &Pos, PWSTR MaxPos, BOOL (*DivPred)(WCHAR))
{
	PWSTR ThisOpt = Pos;

	for(; Pos < MaxPos; Pos++)
		if(IsQuote(*Pos))
		{
			ProceedToQuote(++Pos, MaxPos);
			*(Pos++) = NULL; //null terminate
			return ++ThisOpt;//skip leading quote
		}
		else if(DivPred(*Pos))
		{
			*(Pos++) = NULL; //null terminate
			return ThisOpt; //return beginning of item
		}

	return ThisOpt;
}

//true if FirstPred is true before SecondPred, false otherwise
//does not modify strings, not const for simplicity
BOOLEAN PeekFirstPredBefore(PWSTR const &Pos, PWSTR MaxPos, BOOL (*FirstPred)(WCHAR), BOOL (*SecondPred)(WCHAR))
{
	for(PWSTR ThisOpt = Pos; ThisOpt < MaxPos; ThisOpt++)
		if(IsQuote(*Pos))
		{
			ThisOpt++;
			ProceedToQuote(ThisOpt, MaxPos);
			++ThisOpt;//skip leading quote
		}
		else if(FirstPred(*ThisOpt))
		{
			return TRUE;
		}
		else if(SecondPred(*ThisOpt))
		{
			return FALSE;
		}
	return FALSE; //reached end
}

void FindEnumChoice(PWSTR &Pos, PWSTR MaxPos, SDE_ENUM *Enum IN OUT, BOOLEAN Single)
{
	WCHAR Op; //+ for bitwise or, - for bitwise and-not, = for replace
	
	if(Single)
		Op = L'=';
	else
	{
		Op = *Pos++; //set to first character in option list

		if(Op != L'+' && Op != L'-' && Op != L'=' && Op != '0')
			RaiseTextError(L"Invalid multi-enum operation: use one of +, -, =, 0 to add, remove, replace or zero bits");
	}

	if(Op == L'=')
		Enum->Choice = 0;

	if(Op == L'0')
	{
		Enum->Choice = 0;
		return;
	}

	while(Pos < MaxPos)
	{
		BOOLEAN LastItem = PeekFirstPredBefore(Pos, MaxPos, &IsOptDiv, &IsMultiEnumDiv);
		PWSTR SearchOptName = ExtractThisXAndProceedNext(Pos, MaxPos, Single ? &IsOptDiv : &IsMultiEnumDiv);

		for(ULONG i = 0; i < Enum->ValCount; i++)
		{
			SDE_ENUM_VAL *Val = &Enum->Vals[i];

			if(_wcsicmp(SearchOptName, Val->Name) == 0)
			{
				switch(Op)				
				{
				case L'-':
					Enum->Choice &= ~Val->Value; //'subtract' bits
					break;

				case L'+': //'add' bits
				case L'=': //or accumulate during replacement
					Enum->Choice |= Val->Value;
					break;
				}

				if(Single)
					return;
				else
					goto continue_outer;
			}
		}
		RaiseTextError(L"Invalid enum value", NULL, SearchOptName);

		continue_outer:; //i'd use continue, but it can't skip the nested for loop
		if(LastItem)
			break;
	}
}

void PopulateFromFile(SDE_OPTS *Opts, PCWSTR FName);

void PopulateFromString(SDE_OPTS *Opts, PWSTR Str)
{
	PWSTR Pos = Str;
	PWSTR MaxPos = Str + wcslen(Str);

	SDE_OPTS *ThisOpts = Opts;

	while(Pos < MaxPos)
	{
		SkipSpaces(Pos, MaxPos); //skip any extra spaces

		if(Pos[0] == L'@') //file redirection
		{
			Pos++;
			PopulateFromFile(ThisOpts, ExtractThisXAndProceedNext(Pos, MaxPos, &IsTextDiv));
			ThisOpts = Opts;
			continue;
		}

		PWSTR SearchOptName = ExtractThisXAndProceedNext(Pos, MaxPos, &IsOptDiv);

		OpText Ot(L"Processing option", SearchOptName);

		if(!SearchOptName[0])
			continue;

		for(ULONG i = 0; i < ThisOpts->OptCount; i++)
		{
			SDE_OPT *Opt = &ThisOpts->Opts[i];

			if(_wcsicmp(SearchOptName, Opt->Name) == 0)
				switch(Opt->Type)
				{
				case SptString:
					{
						auto_szpod<WCHAR> StrCpy = ExtractThisXAndProceedNext(Pos, MaxPos, &IsTextDiv);
						Opt->String = StrCpy.release_unsafe();
						Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED | SDE_OPT_DATA_OWNERSHIP;
						ThisOpts = Opts;
					}
					goto continue_outer;

				case SptInt32:
					swscanf_s(ExtractThisXAndProceedNext(Pos, MaxPos, &IsTextDiv), L"%lu", &Opt->Int32);
					Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED;
					ThisOpts = Opts;
					goto continue_outer;

				case SptInt32Hex:
					swscanf_s(ExtractThisXAndProceedNext(Pos, MaxPos, &IsTextDiv), L"%lx", &Opt->Int32);
					Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED;
					ThisOpts = Opts;
					goto continue_outer;

				case SptBool:
					swscanf_s(ExtractThisXAndProceedNext(Pos, MaxPos, &IsTextDiv), L"%lu", &Opt->Bool);
					Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED;
					Opt->Bool = Opt->Bool != 0;
					ThisOpts = Opts;
					goto continue_outer;

				case SptPresent:
					Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED;
					Opt->Bool = TRUE;
					ThisOpts = Opts;
					goto continue_outer;

				case SptEnum:
				case SptMultiEnumOr:
					FindEnumChoice(Pos, MaxPos, Opt->Enum, Opt->Type == SptEnum);
					Opt->Flags |= SDE_OPT_SET | SDE_OPT_SPECIFIED;
					ThisOpts = Opts;
					goto continue_outer;

				case SptSubList:
					ThisOpts = Opt->SubList; //set to sublist, reset on actual value
					goto continue_outer;

				case SptOther:
				default:
					RaiseTextError(L"Bad option data type", L"Parsing", Opt->Name);
					break;
				}
		}
	RaiseTextError(L"Invalid option", NULL, SearchOptName); //no options matched

	Ot;

	continue_outer:; //i'd use continue, but it can't skip the nested for loop
	}
}

void PopulateFromFile(SDE_OPTS *Opts, PCWSTR FName)
{
	OpText Ot(L"Reading options from file", FName);

	HANDLE File = INVALID_HANDLE_VALUE;

	while(File == INVALID_HANDLE_VALUE)
	{
		File = CreateFile(FName, FILE_READ_DATA, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | SECURITY_IDENTIFICATION, NULL);

		RaiseOnWin32ErrorT(File, INVALID_HANDLE_VALUE, FALSE);
	}

	DWORD Size = GetFileSize(File, NULL), SizeRead;
	auto_szpod<CHAR> FileData = auto_szpod_empty(Size + sizeof(WCHAR));
	
	RaiseOnWin32ErrorT(ReadFile(File, FileData, Size, &SizeRead, NULL));

	FileData[SizeRead + 0] = 0;
	FileData[SizeRead + 1] = 0;

	//read byte order
	if(FileData[0] == 0xFF && FileData[1] == 0xFE) //little-endian unicode ENDIAN_UNSAFE
		PopulateFromString(Opts, reinterpret_cast<PWSTR>(FileData + 2));
	else //assume mbcs
	{
		auto_szpod<WCHAR> UnicodeStr;
		DWORD ReqSz = MultiByteToWideChar(CP_ACP, 0, FileData, -1, NULL, 0);
		RaiseOnWin32ErrorT(ReqSz);
		UnicodeStr.realloc_ct(ReqSz);
		RaiseOnWin32ErrorT(MultiByteToWideChar(CP_ACP, 0, FileData, -1, UnicodeStr, ReqSz));

		PopulateFromString(Opts, UnicodeStr);
	}
}

void PopulatePrefsFromCmdLine(SDE_OPTS *Opts, PCWSTR OrigStr)
{
	auto_szpod<WCHAR> StrCopy = OrigStr;
	PWSTR Str = StrCopy;

	ExtractThisXAndProceedNext(Str, Str + wcslen(Str), &IsTextDiv); //skip own program entry

	PopulateFromString(Opts, Str);
}

}//namespace CmdLineParser
